package mbsl.commons.base;

import static mbsl.util.SWTUtils.Direction.down;
import static mbsl.util.SWTUtils.Direction.left;
import static mbsl.util.SWTUtils.Direction.right;
import static mbsl.util.SWTUtils.Direction.up;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import mbsl.main.StatusBarController;
import mbsl.ui.Combo;
import mbsl.ui.SpinnerPanel;
import mbsl.ui.UIUtils;
import mbsl.ui.interfaces.Listener;
import mbsl.util.SWTUtils;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

/**
 * This is the foundation for an edit dialog, used when adding a new element or editing an existing one.
 * @author aschj
 */
public abstract class ElementEditDialog {
	/** Add a listener to the save button */
	public void addSaveListener(SelectionListener pListener) {
		mSave.addSelectionListener(pListener);
	}
	public void addCloseListener(Listener pListener) {
		if (mCloseListeners == null)
			mCloseListeners = new ArrayList<Listener>();
		
		mCloseListeners.add(pListener);
	}
	/** Opens the window */
	public void open() {
		mWindow.setVisible(true);
		
		mTitle.forceFocus();
		
		SWTUtils.block(mWindow);
	}
	/** Closes and disposes the window */
	public void close() {
		notifyCloseListeners();
		
		mWindow.dispose();
	}
	protected void notifyCloseListeners() {
		if (mCloseListeners != null)
			for (Listener listener : mCloseListeners)
				listener.alert();
	}
	/**
	 * @param pParentWindow The parent window, to which the dialog belongs.
	 * @param pTitle The title of the window, as well as the label at the top.
	 */
	protected ElementEditDialog(Shell pParentWindow, int[] pColor) {
		mWindow = new Shell(pParentWindow, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);

		if (mTitleFont == null) {
			FontData font = pParentWindow.getFont().getFontData()[0];
			
			font.setHeight(18);
			font.setStyle(SWT.BOLD);
			
			mTitleFont = new Font(pParentWindow.getDisplay(), font);
		}
		
		mWindow.setLayout(new FormLayout());
		
		createTitle(pColor);
		createStatusBar();
		
		setUpForm();
		
		mWindow.pack();
	}
	/** Returns the tools to manipulate the status bar at the bottom of this dialog. */
	public StatusBarController getStatusBar() {
		return mStatusBar;
	}
	/**
	 * Sets the window title to the passed String.
	 * <p>
	 * Insert %s at the spot where the contents of the title field should go.
	 */
	public void setWindowTitle(String pTitle) {
		mWindowTitle = pTitle;
		
		if (!mTitle.getText().equals(mTitleShadowText))
			mWindow.setText(String.format(mWindowTitle, mTitle.getText()));
		else
			mWindow.setText(String.format(mWindowTitle, ""));
	}
	public Text getTitleField() {
		return mTitle;
	}
	/** 
	 * Sets up the form panel of the dialog.  Override this method if you require other layouts, backgrounds, etc. 
	 * <p>
	 * Note that {@link #mForm} has not been initialised yet if you override this method, so doing<pre>
	 * mForm = new Composite(mWindow, style)
	 * </pre>should have priority.
	 */
	protected void setUpForm() {
		mForm = new Composite(mWindow, SWT.NONE);
		
		GridLayout layout = new GridLayout();
		
		layout.numColumns = 3;
		
		mForm.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, mTitle, 20),
				SWTUtils.getMeta(left, 0, 0),
				SWTUtils.getMeta(right, 100, 0),
				SWTUtils.getMeta(down, 100, -20)
		));
		mForm.setLayout(layout);
		
		addFormComponents(mForm);
		
		Composite savePanel = new Composite(mForm, SWT.NONE);
		GridData savePanelLayout = getDefaultGridData();
		
		savePanelLayout.horizontalSpan = 3;

		savePanel.setLayoutData(savePanelLayout);
		savePanel.setLayout(new FormLayout());
		
		Button cancelButton = new Button(savePanel, SWT.PUSH);
		mSave = new Button(savePanel, SWT.PUSH);
		
		cancelButton.setText("Cancel");
		mSave.setText("Save");
		
		cancelButton.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, 0, 0),
				SWTUtils.getMeta(down, 100, 0),
				SWTUtils.getMeta(right, 100, 0)
		));
		
		FormData saveButtonLayout = SWTUtils.getFormData(
				SWTUtils.getMeta(up, 0, 0),
				SWTUtils.getMeta(down, 100, 0),
				SWTUtils.getMeta(right, cancelButton, 0)
		);
		
		saveButtonLayout.width = cancelButton.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
		
		mSave.setLayoutData(saveButtonLayout);
		
		cancelButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent pE) {
				close();
			}
		});
	}
	/** Adds a spinner panel designed for selecting a year between 1970 and the current year + 1. The default selection will be the current year. */
	protected SpinnerPanel addYearSpinner(int pMinYear) {
		int thisYear = new GregorianCalendar().get(Calendar.YEAR);
		
		return new SpinnerPanel(mForm, thisYear + 1, pMinYear, thisYear);
	}
	/** Adds a combo box.  This is by default set to fill all available horizontal space. */
	protected Combo addCombo(boolean pEditable) {
		Combo combo;
		
		if (!pEditable)
			combo = new Combo(mForm, null, SWT.DROP_DOWN | SWT.READ_ONLY);
		else
			combo = new Combo(mForm, SWT.DROP_DOWN);
		
		combo.getWidget().setLayoutData(getDefaultGridData());
		
		return combo;
	}
	protected Combo addCombo() {
		return addCombo(true);
	}
	/** Adds a filler. */
	protected void addFiller() {
		UIUtils.insertFiller(mForm);
	}
	/** Returns the default grid data object for non-label components (fill horizontally, center vertically). */
	protected GridData getDefaultGridData() {
		return new GridData(SWT.FILL, SWT.CENTER, true, false);
	}
	/** Adds a label with the provided text.  The text is right-aligned by default. */
	protected Label addLabel(String pText) {
		Label label = new Label(mForm, SWT.TRAIL);
		
		label.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
		label.setText(pText);
		
		return label;
	}
	/** Adds a text field.  This is by default set to fill all available horizontal space. */
	protected Text addTextField() {
		Text textField = new Text(mForm, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
		
		textField.setLayoutData(getDefaultGridData());
		
		return textField;
	}
	/** 
	 * Add components to the form panel.
	 * <p>
	 * By default (that is if setUpForm() isn't overridden), the panel has a three-column layout.
	 * The components are added to the panel following the rules of the GridLayout which dictates
	 * it, but basically it's left to right, and adding a new row when all three columns are
	 * filled.  This makes fillers necessary, and these are easily available with the addFiller()
	 * method.
	 * <p>
	 * Also by default, OK and Cancel buttons will be added to the bottom of the panel.
	 * <p>
	 * For spanning, you need to set up your own {@link org.eclipse.swt.layout.GridData GridData},
	 * and attach it to the widget with setLayoutData().
	 * @see #addCombo()
	 * @see #addFiller()
	 * @see #addLabel(String)
	 * @see #addTextField()
	 * @see #addYearSpinner()
	 */
	protected abstract void addFormComponents(Composite form);
	/** Creates the Title label */
	private void createTitle(int[] pColor) {
		mTitle = new Text(mWindow, SWT.CENTER | SWT.SINGLE | SWT.BORDER);

		mTitle.setFont(mTitleFont);
		mTitle.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, 0, 5), 
				SWTUtils.getMeta(left, 0, 5), 
				SWTUtils.getMeta(right, 100, -5),
				SWTUtils.getMeta(down, 0, mTitle.computeSize(SWT.DEFAULT, SWT.DEFAULT).y + 5)
		));
		mTitle.setBackground(new Color(mWindow.getDisplay(), pColor[3], pColor[4], pColor[5]));
		
		mTitle.addModifyListener(new ModifyListener() {
			@Override
			public void modifyText(ModifyEvent pE) {
				if ((mWindowTitle.contains("%s") || mWindowTitle.contains("%1$s")) && !mTitle.getText().equals(mTitleShadowText))
					mWindow.setText(String.format(mWindowTitle, mTitle.getText()));
			}
		});
		
		mTitle.addFocusListener(new FocusListener() {
			@Override
			public void focusLost(FocusEvent pE) {
				if (mTitle.getText().trim().isEmpty() && mTitleShadowText != null) {
					mTitle.setForeground(mWindow.getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND));
					
					mTitle.setText(mTitleShadowText);
				}
			}
			
			@Override
			public void focusGained(FocusEvent pE) {
				if (mTitle.getText().equals(mTitleShadowText)) {
					mTitle.setForeground(mWindow.getDisplay().getSystemColor(SWT.COLOR_TITLE_FOREGROUND));
					
					mTitle.setText("");
				}
			}
		});
	}
	/** The text shown in the title field when it's empty */
	public void setTitleShadowText(String pText) {
		mTitleShadowText = pText;
	}
	
	private String mTitleShadowText;
	
	/** Creates the status bar, and the interface through which one can communicate with it */
	private void createStatusBar() {
		Composite statusPanel = new Composite(mWindow, SWT.NONE);
		
		statusPanel.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, 100, -20),
				SWTUtils.getMeta(left, 0, 0),
				SWTUtils.getMeta(right, 100, 0),
				SWTUtils.getMeta(down, 100, 0)
		));
		statusPanel.setLayout(new FormLayout());
		
		Label horizontalSeparator = SWTUtils.getHorizontalBar(statusPanel);
		
		horizontalSeparator.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, 0, 0),
				SWTUtils.getMeta(down, 0, 1),
				SWTUtils.getMeta(left, 0, 1),
				SWTUtils.getMeta(right, 100, -1)
		));
		
		final ProgressBar progressBar = new ProgressBar(statusPanel, SWT.HORIZONTAL | SWT.INDETERMINATE);
		
		progressBar.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, horizontalSeparator, STATUSPANEL_MARGIN),
				SWTUtils.getMeta(down, 100, -STATUSPANEL_MARGIN),
				SWTUtils.getMeta(right, 100, -STATUSPANEL_MARGIN),
				SWTUtils.getMeta(left, 60, 0)
		));
		progressBar.setState(SWT.PAUSED);
		
		Label verticalSeparator = SWTUtils.getVerticalBar(statusPanel);
		
		verticalSeparator.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, horizontalSeparator, 3),
				SWTUtils.getMeta(down, 100, -3),
				SWTUtils.getMeta(right, progressBar, -STATUSPANEL_MARGIN)
		));
		
		final Label info = new Label(statusPanel, SWT.NONE);
		
		info.setLayoutData(SWTUtils.getFormData(
				SWTUtils.getMeta(up, horizontalSeparator, STATUSPANEL_MARGIN),
				SWTUtils.getMeta(down, 100, -STATUSPANEL_MARGIN),
				SWTUtils.getMeta(left, 0, STATUSPANEL_MARGIN),
				SWTUtils.getMeta(right, verticalSeparator, -STATUSPANEL_MARGIN)
		));
		
		mStatusBar = new StatusBarController() {
			@Override
			public void release() {}
			@Override
			public boolean requestOwnership(boolean pForce) {
				return false;
			}
			@Override
			public boolean requestOwnership(StatusBarController pController) {
				return false;
			}
			@Override
			public void setInfo(String pMessage) {
				if (pMessage == null)
					info.setText("");
				else
					info.setText(pMessage);
			}
			@Override
			public void setProgress(int pPercentage) {
				if (pPercentage >= 100)
					progressBar.setState(SWT.PAUSED);
				else
					progressBar.setState(SWT.NORMAL);
			}
			@Override
			public void setStatus(Status pStatus) {}
		};
	}
	/** A margin used by the status panel.  For more air, set higher values. */
	private static final int STATUSPANEL_MARGIN = 2;
	/** 
	 * The Shell used for this dialog.
	 */
	protected Shell mWindow;
	/**
	 * The form component on which the fields and buttons should be placed.
	 * <p>
	 * By default, it has a GridLayout, and it is required to keep that if the convenience methods for
	 * building the form are used.  If another layout is required, children have to override the
	 * setUpForm() method. 
	 * <p>
	 * This should not be exposed by children.
	 * @see #setUpForm()
	 */
	private String mWindowTitle;
	protected Composite mForm;
	private Button mSave;
	private Text mTitle;
	private StatusBarController mStatusBar;
	private List<Listener> mCloseListeners;
	private static Font mTitleFont;
}
